//Copyright 2009-2010 Virzì Giuseppe - info@giuseppevirzi.it
//http://www.giuseppevirzi.it
//
//This module may be used under the terms of the following license:
//
//AL, Apache License, http://www.apache.org/licenses
//
//Please contact the author if you need another license.
//This APIs are provided "as is", without warranties of any kind.


package it.giuseppevirzi.net.multimedia.vcastapi;

import ch.inventec.utils.Base64Coder;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONArray;
import org.json.JSONException;


/**
 * @author <a href="http://www.giuseppevirzi.it">Giuseppe Virz&igrave;</a>
 */
public class VCastAPI{

    //username required to login the VCast platform.
    private String username;
    
    //password required to login the VCast platform.
    private String password;
    
    /**
     * @param username username required to login the VCast platform.
     * @param password password required to login the VCast platform.
     */
    public VCastAPI(String username, String password){
        this.username=username;
        this.password=password;
    }
    
    /**
     * Get the channels list
     * @return A JSON Object which is an array of channels.
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * To better clarify the structure of the channels array here is an example 
     * of the JSON object provided by the server:
     * 
     * [ {"name":"RaiRadio1","type":"audio"},
     * {"name":"RaiRadio2","type":"audio"},
     * {"name":"RaiRadio3","type":"audio"},
     * 		......
     * 
     * {"name":"Rai1","type":"video"},
     * {"name":"Rai2","type":"video"},
     * {"name":"Rai3","type":"video"},
     *          ......                ]
     * </pre>
     */
    public JSONArray channels() 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   WrongResponseFormatException, VCastAPIException{
        try {
            return new JSONArray(callAPI(API.ALL_CHANNELS));
        } catch (JSONException ex) {
            Logger.getLogger(VCastAPI.class.getName()).log(Level.SEVERE, null, ex);
            throw new WrongResponseFormatException();
        }
    }
    
    /**
     * Returns all available channels. 
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * The client can set a filter on the 
     * channel type (radio or tv), but currently the request returns the whole
     * list containing both types in any request. That's because the list 
     * parsing is performed by the client.
     * </pre>
     * @return A JSON Object which is an array of channels.
     * <pre>
     * To better clarify the structure of the channels array here is an example 
     * of the JSON object provided by the server:
     * 
     * [ {"name":"RaiRadio1","type":"audio"},
     * {"name":"RaiRadio2","type":"audio"},
     * {"name":"RaiRadio3","type":"audio"},
     * 		......
     * 
     * {"name":"Rai1","type":"video"},
     * {"name":"Rai2","type":"video"},
     * {"name":"Rai3","type":"video"},
     *          ......                ]
     * </pre>
     */
    public JSONArray channels(String type) 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   WrongResponseFormatException, VCastAPIException{
        try {
            if (type.equals("video")) {
                return new JSONArray(callAPI(API.VIDEO_CHANNELS));
            } else if (type.equals("audio")) {
                return new JSONArray(callAPI(API.AUDIO_CHANNELS));
            } else if (type.equals("")) {
                return new JSONArray(callAPI(API.ALL_CHANNELS));
            } else {
                return null;
            }
        } catch (JSONException ex) {
            Logger.getLogger(VCastAPI.class.getName()).log(Level.SEVERE, null, ex);
            throw new WrongResponseFormatException();
        }
    }
    
    /**
     * Get the recordings list.
     * @return A JSON object. 
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * It is an array whose elements are the parameters 
     * describing the recording:
     * - id_rec : identification number of the recording
     * - channel : channel name
     * - channel_type : channel type (audio or video)
     * - title : name of the recording
     * - from_time : start time
     * - to_time : end time
     * - rec_time : duration of the recording
     * - repeat : periodicity of the recording (not repeated/daily/weekly/from monday to friday/from monday to saturday)
     * - retention :
     * - format : media format (ipod for video streams, mp3 for audio ones)
     * - faucetacc : url for opening the faucet modification page for the recording
     * 
     * An example of the JSON object returned by the server is the following:
     * 
     * [ 
     *   {
     *    "id_rec":"411202",
     *    "channel":"RadioDeeJay",
     *    "channel_type":"audio",
     *    "title":"Volo del mattino",
     *    "from_time":"2008-02-04 09:00:00",
     *    "to_time":"2008-02-04 10:00:00",
     *    "rec_time":"00:00",
     *    "repeat":"no_repeat",
     *    "retention":"3",
     *    "format":"mp3",
     *    "faucetacc":"http://www.vcast.it/faucetpvr/update.php?qwt=w=HMgTzN"
     *   },
     * 
     *   {
     *    "id_rec":"411201",
     *    "channel":"Rai1",
     *    "channel_type":"video",
     *    "title":"Tg1",
     *    "from_time":"2008-02-04 20:00:00",
     *    "to_time":"2008-02-04 20:30:00",
     *    "rec_time":"00:00",
     *    "repeat":"daily",
     *    "retention":"3",
     *    "format":"ipod",
     *    "faucetacc":"http://www.vcast.it/faucetpvr/update.php?qwt=w=NRUTzN"
     *   },
     * 
     *   {
     *    "id_rec":"411204",
     *    "channel":"La7",
     *    "channel_type":"video",
     *    "title":"Reservoir dogs",
     *    "from_time":"2008-02-04 21:00:00",
     *    "to_time":"2008-02-04 23:30:00",
     *    "rec_time":"00:00",
     *    "repeat":"no_repeat",
     *    "retention":"3",
     *    "format":"ipod",
     *    "faucetacc":"http://www.vcast.it/faucetpvr/update.php?qwt=w=TMQTzN"
     *   }
     * ]
     * </pre>
     */
    public JSONArray recordings() 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   WrongResponseFormatException, VCastAPIException{
        try {
            return new JSONArray(callAPI(API.RECORDINGS).replaceAll("\\\\", ""));
        } catch (JSONException ex) {
            Logger.getLogger(VCastAPI.class.getName()).log(Level.SEVERE, null, ex);
            throw new WrongResponseFormatException("There is an error in the"+
            						   " JSON Object obtained from the server",ex);
        }
    }
    
    /**
     * Create a new recording.
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * An example of the JSON object sent to the server to specify a 
     * new recording insertion is:
     * 
     * {
     * "from_time":"2008-02-04 11:00:00",
     *  "ch_name":"Mtv",
     *  "to_time":"2008-02-04 12:30:00",
     *  "ch_type":"video",
     *  "retention":"3",
     *  "repeat":"no_repeat",
     *  "format":"ipod",
     *  "title":"Into the music"
     * }
     * 
     * The allowed value for the different field are:
     * ch_type can be 'audio, 'video' (see {@link CHANNEL_TYPE})
     * format can be 'ipod', 'psp', '3gpp', 'divx', 'mp3', 'oggvorb', 'appletv'  (see {@link FORMAT})
     * repeat can be 'no_repeat', 'daily', 'weekly', 'mon_fri', 'mon_sat' (see {@link REPEAT})
     * 
     * The field retention and format are not mandatory, if not present the 
     * default for yhe user will be inserted. 
     * </pre>
     * @param start String which rapresents the begin time 
     * formatted as "yyyy-MM-dd hh-mm-ss"
     * @param stop String which rapresents the finish time 
     * formatted as "yyyy-MM-dd hh-mm-ss"
     * @param channel_name The name of the channel
     * @param channel_type The type of the channel (see {@link CHANNEL_TYPE})
     * @param retention Indicates the numbers of days before registrations are deleted
     * @param repeat Indicates the way that recording will be repeated over time.
     * Allowed value are in {@link REPEAT}
     * @param format The format of the recorded file. Allowed value are in {@link FORMAT}
     * @param title The title of the Recording
     * @return True if the creation has been done, false otherwise.
     */
    public boolean createRecording( String start,
                                    String stop,
                                    String channel_name,
                                    CHANNEL_TYPE channel_type,
                                    Integer retention,
                                    REPEAT repeat,
                                    FORMAT format,
                                    String title
                                    ) 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        //create an array of arguments containing only one argument: the JSON 
        //object
        String[] args = new String[1];
        args[0]="{"+ 
                "\"from_time\":\""+start+"\","+
                "\"ch_name\":\""+channel_name+"\","+
                "\"to_time\":\""+stop+"\","+
                "\"ch_type\":\""+channel_type.getValue()+"\","+
                "\"retention\":\""+retention+"\","+
                "\"repeat\":\""+repeat.getValue()+"\","+
                "\"format\":\""+format.getValue()+"\","+
                "\"title\":\""+title+"\""+
                "}";
        System.out.println(args[0]);
        //setting parameter of API.CREATE_RECORDING
        API.CREATE_RECORDING.setParams(args);
        
        if( !callAPI(API.CREATE_RECORDING).equals("") ){
            return true;
        }
        return false;
    }
    
    /**
     * Modify an existing recording.
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * An example of the JSON object sent to the server to specify a 
     * new recording insertion is:
     * 
     * {
     * "id_rec":"411206",
     * "from_time":"2008-02-04 11:00:00",
     *  "ch_name":"Mtv",
     *  "to_time":"2008-02-04 12:30:00",
     *  "ch_type":"video",
     *  "retention":"3",
     *  "repeat":"no_repeat",
     *  "format":"ipod",
     *  "title":"Into the music"
     * }
     * </pre>
     * @param id The id of the recording to be modified
     * @param start String which rapresents the new begin time 
     * formatted as "yyyy-MM-dd hh-mm-ss"
     * @param stop String which rapresents the new finish time 
     * formatted as "yyyy-MM-dd hh-mm-ss"
     * @param channel_name The new name for the channel
     * @param channel_type The new type of the channel (see {@link CHANNEL_TYPE})
     * @param retention Indicates the numbers of days before registrations are deleted
     * @param repeat Indicates the way that recording will be repeated over time.
     * Allowed value are in {@link REPEAT}
     * @param format The new format of the recorded file. Allowed value are in {@link FORMAT}
     * @param title The new title of the Recording
     * @return true if the recording has been effectively modifyed. False otherwise.
     */
    public boolean modifyRecording( int id,
                                    String start,
                                    String stop,
                                    String channel_name,
                                    CHANNEL_TYPE channel_type,
                                    Integer retention,
                                    REPEAT repeat,
                                    FORMAT format,
                                    String title
                                    ) 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        String[] args = new String[1];
        args[0]="{"+ 
                "\"id_rec\":\""+id+"\","+
                "\"from_time\":\""+start+"\","+
                "\"ch_name\":\""+channel_name+"\","+
                "\"to_time\":\""+stop+"\","+
                "\"ch_type\":\""+channel_type.getValue()+"\","+
                "\"retention\":\""+retention+"\","+
                "\"repeat\":\""+repeat.getValue()+"\","+
                "\"format\":\""+format.getValue()+"\","+
                "\"title\":\""+title+"\""+
                "}";
        //setting parameter of API.DELETE_RECORDING
        API.MODIFY_RECORDING.setParams(args);
        
        if( !callAPI(API.MODIFY_RECORDING).equals("") ){
            return true;
        }
        return false;
    }
    
    /**
     * Delete an existing recording.
     * @param id The id of the Recording to be cancelled.
     * @return true if the recording has been effectively cancelled.
     */
    public boolean deleteRecording(int id) 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        //create an array of arguments containing only one argument: the id
        String[] args = new String[1];
        args[0]=String.valueOf(id);
        
        //setting parameter of API.DELETE_RECORDING
        API.DELETE_RECORDING.setParams(args);
        
        if( !callAPI(API.DELETE_RECORDING).equals("") ){
            return true;
        }
        return false;
    }
    
    /**
     * Return Faucet RSS feed for the user.
     * @return the feed
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * An example of the JSON object returned by the server is the following:
     * 
     * {
     *  "feed_url":"http://www.vcast.it/JPodcast/Vfaucet/........"
     * }
     * </pre>
     */
    public String feed() 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        return callAPI(API.FEED).replaceAll("\\\\","");
    }
    
    /**
     * Return Faucet internal user identifier.
     * @return the faucet feed
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * An example of the JSON object returned by the server is the following:
     * 
     * {
     *  "id_usr":"411202"
     * }
     * </pre>
     */
    public String faucetID() 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        return callAPI(API.FAUCETID);
    }
    
    /**
     * Return the URL that publishes the list of the active recording criteria.
     * @return address to user's feed
     * <br/><br/>From the official <a href="http://www.vcast.it/TestS/?xml=api_specification.xml">api specifications</a>:
     * <pre>
     * An example of the JSON object returned by the server is the following:
     * 
     * {
     *  "active_rec_url":"http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/activerecordings?K=yreorpeutrreuqirpqwiu"
     * }
     * </pre>
     */
    public String activeFeedURL() 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException{
        return callAPI(API.ACTIVE_FEED_URL).replaceAll("\\\\","");
    }
    
    /**
     * Calls the selected api
     * @param api the API to call
     * @return the response String resulting from the calling
     */
    private String callAPI(API api) 
            throws BadRequestException, UnauthorizedAccessException, 
                   RecordingNotFoundException, InternalServerErrorException,
                   VCastAPIException {
        try {
            return readResponse(
                        connect(api.getURL(), 
                                api.method,
                                authorizations(this.username,this.password),
                                api.getParams()
                               )
                    );
        } catch (IOException e) {
            throw new VCastAPIException("There was en IO error. Impossible to read server response",e);
        }
    }
    
    /**
     * Creates a String from the BufferedReader obtained from the connect method
     * @param in BufferedReader returned by connect
     * @return A string containing the response
     * @throws java.io.IOException
     */
    private String readResponse(BufferedReader in) throws IOException {
        String inputLine;
        String response="";
        if(in!=null){
            while ((inputLine = in.readLine()) != null) {
                response = response + inputLine;
            }
            in.close();
        }
        return response;
    }
    
    /**
     * Connects to a url using the specified method and retrieves the response
     * @param method The HTTP method, GETo or POST
     * @param url URL to connect to
     * @param authorizations A string containing a Base64 rapresentation of
     * "Basic username:password"
     * @return A BufferedReader containing the response
     * @throws java.net.ProtocolException
     * @throws java.io.IOException
     */
    private BufferedReader connect( URL url, String method, String authorizations, String params) 
            throws  BadRequestException, UnauthorizedAccessException, 
                    RecordingNotFoundException, InternalServerErrorException,
                    VCastAPIException {
        try {

            //get a HTTP connection on that url
            HttpURLConnection huc = (HttpURLConnection) url.openConnection();
            huc.setRequestMethod(method);

            //Setting the authorization
            huc.setRequestProperty("Authorization", authorizations);


            if (method.equals("POST")) {
                huc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

                huc.setRequestProperty("Content-Length", "" + Integer.toString(params.getBytes().length));
                huc.setRequestProperty("Content-Language", "en-US");

                huc.setUseCaches(false);
                huc.setDoInput(true);
                huc.setDoOutput(true);

                DataOutputStream wr = new DataOutputStream(huc.getOutputStream());
                wr.writeBytes(params);
                wr.flush();
                wr.close();
            }
            huc.connect();

            //http response code
            int code = huc.getResponseCode();

            switch (code) {
                case 400:
                    throw new BadRequestException("Bad request in those cases in which some of the "+
						                          "parameters are not provided, or the channel type "+
						                          "is not 'audio' nor 'video', or if the recording "+
						                          "time is longer than 3 hours.");
                case 401:
                    throw new UnauthorizedAccessException("Verify username and Password");
                case 404:
                    throw new RecordingNotFoundException("Recording not found. Maybe you are "+
                            							 "trying to delete a non-existent recording ");
                case 500:
                    throw new InternalServerErrorException("Internal server error in case some"+ 
								                           "modification errors occur (that is any error that"+ 
								                           "can occur during the insert method, used in "+
								                           "conjuction with the delete method to do the "+
								                           "modify) ");
                default:
                    {
                        BufferedReader in = new BufferedReader(new InputStreamReader(huc.getInputStream()));
                        return in;
                    }
            }
        } catch (IOException ex) {
            throw new VCastAPIException("Impossible to read the response from Server.",ex);
        }
    }
    
    /**
     * Creates the required authorization string to connect to VCast platform
     * @param username the username
     * @param password the user password
     * @return the encoded String
     */
    private String authorizations(String username, String password){
        return "Basic "+Base64Coder.encodeString(username+":"+password);
    }
    
    /**
     * Contains the correct values assignable to channel_type
     */
    public enum CHANNEL_TYPE{
    	/**
    	 * The channel is a Radio channel
    	 */
        AUDIO("audio"),
        
        /**
    	 * The channel is a Television channel
    	 */
        VIDEO("video");
        
        private final String value;
        
        /**
         * Builds the enum with the selected value
         * @param value The enum's value
         */
        CHANNEL_TYPE(String value){
            this.value=value;
        }
        
        /**
         * Refers to the selected value
         * @return The enum's value
         */
        public String getValue(){
            return this.value;
        }
    };
    
    /**
     * Contains the correct values to assign to the repeat field
     *
     */
    public enum REPEAT{
        /**
         * The recording will not be repeated over time
         */
    	NO_REPEAT("no_repeat"),
    	/**
    	 * The recording will be repeated every day
    	 */
        DAILY("daily"), 
        /**
    	 * The recording will be repeated every week
    	 */
        WEEKLY("weekly"), 
        /**
    	 * The recording will be repeated from monday to friday every week.
    	 */
        MON_FRI("mon_fri"), 
        /**
    	 * The recording will be repeated from monday to saturday every week.
    	 */
        MON_SAT("mon_sat");
    
    	private final String value;
        
        /**
         * Build the enum with the selected value
         * @param value The enum's value
         */
    	REPEAT(String value){
            this.value=value;
        }
        
    	/**
    	 * Refers to the selected value
    	 * @return The enum's value.
    	 */
        public String getValue(){
            return this.value;
        }
    };
    
    /**
     * Encloses the correct values assignable to the format field of the requests.
     */
    public enum FORMAT{
    	/**
    	 * A file viewable on an iPod device
    	 */
        IPOD("ipod"),
        
        /**
    	 * A file viewable on an PSP device
    	 */
        PSP("psp"),
        
        /**
    	 * A 3gpp file
    	 */
        _3GPP("3gpp"),
        
        /**
    	 * A DivX file
    	 */
        DIVX("divx"),
        
        /**
    	 * An mp3 audio file
    	 */
        MP3("mp3"),
        
        /**
    	 * An ogg-vorbis file
    	 */
        OGGVORB("oggvorb"),
        
        /**
    	 * An AppleTV file
    	 */
        APPLETV("appletv");
        
        private final String value;
        
        /**
         * Builds the enum with the chosen value
         * @param value the wanted value
         */
        FORMAT(String value){
            this.value=value;
        }
        
        /**
         * Returns the enum's value
         * @return The enum's value
         */
        public String getValue(){
            return this.value;
        }
    };
    
    /**
     * Encloses all apis. Each element describes the URL and the method to 
     * access it.
     */
    private enum API {
  /*1*/ VIDEO_CHANNELS("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/channels?type=video","GET") {
            public void setParams(String[] args){return;};
        },
        
        AUDIO_CHANNELS("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/channels?type=audio","GET"){
            public void setParams(String[] args){return;};
        },
        
        ALL_CHANNELS("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/channels","GET"){
            public void setParams(String[] args){return;};
        },
        
  /*2*/ RECORDINGS("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/recordings","GET"){
            public void setParams(String[] args){return;};
        },
        
  /*3*/ CREATE_RECORDING("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/recordings","POST"){
            public void setParams(String[] args){
                this.params=args[0];
            }
        },
        
  /*4*/ MODIFY_RECORDING("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/recordings","POST"){
            public void setParams(String[] args){
                this.params=args[0];
            }
        },
  
  /*5*/ DELETE_RECORDING("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/delete_recording","GET"){
            public void setParams(String[] args){
                this.address=this.address+"?id_rec="+args[0];
            }
        },
  
  /*6*/ FEED("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/feed","GET"){
            public void setParams(String[] args){return;};
        },
        
  /*7*/ FAUCETID("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/faucetid","GET"){
            public void setParams(String[] args){return;};
        },
        
  /*8*/ ACTIVE_FEED_URL("http://www.vcast.it/faucetpvr/api/1.0/server_rest.php/activerecfeedurl","GET"){
            public void setParams(String[] args){return;};
        };

        //the address of the api
        protected String address;
        
        //Post or Get Parameters
        protected String params="";
        
        //the method GET or POST
        private final String method;
        
        /**
         * Constructs each element of this enum
         * @param address the api's address
         * @param method the api's access method
         */
        API(String address, String method) {
            this.address = address;
            this.method = method;
        }
        
        public abstract void setParams(String[] args);
        
        public String getParams(){
           return this.params;
        }
        
        /**
         * Returns the URL to connect to call the relative API
         * @return the api's URL
         */
        public URL getURL() {
            try {
                return new URL(address);
            } catch (MalformedURLException ex) {
                Logger.getLogger(VCastAPI.class.getName()).log(Level.SEVERE, null, ex);
                return null;
            }
        }
        
        /**
         * Returns a String containing the api's address
         * @return api's address
         */
        @SuppressWarnings("unused")
		public String getAddress(){
            return address;
        }
    }
}